package socket;

import java.io.*;
import java.net.ServerSocket;
import java.net.Socket;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;

/**
 * 聊天室服务端:将第3版的数组换成了集合，那样就不用扩容和缩容了，
 * 直接用集合的API就可以解决客户端上下线的问题，第4版只是服务端有变化，客户端没变化
 *
 * @author XingHaiYang
 * @date 2022/9/15 18:52
 */
public class Server4 {
    private ServerSocket serverSocket;
    /*
        在Server上定义一个属性，这样所有的内部类实例ClientHandler都可以看到它
        从而将这个属性作为所有ClientHandler共享数据的地方。
     */
//    private PrintWriter[] allOut = {};
    private Collection<PrintWriter> allOut = new ArrayList<>();

    public Server4() {
        try {
            System.out.println("正在启动服务端...");
            /*
                java.net.BindException: address already in use
                原因:服务端申请的端口被系统其它程序占用了。
                解决办法:
                1:后期遇到该问题通常是重复启动了两次服务端导致的，需要杀死之前启动的
                  服务端进程
                2:更换端口号
             */
            serverSocket = new ServerSocket(8088);
            System.out.println("服务端启动完毕!");
        } catch (IOException e) {
            e.printStackTrace();
        }

    }

    public void start() {
        try {
            while (true) {
                System.out.println("等待客户端链接...");
                Socket socket = serverSocket.accept();
                System.out.println("一个客户端链接了!");
                //启动一个线程来处理与该客户端的交互
                Runnable handler = new ClientHandler(socket);
                Thread t = new Thread(handler);
                t.start();

            }

        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        Server4 server = new Server4();
        server.start();
    }

    /**
     * 该线程任务是负责与指定的客户端进行交互
     */
    private class ClientHandler implements Runnable {
        private Socket socket;
        private String host;//记录远端计算机地址信息

        public ClientHandler(Socket socket) {
            this.socket = socket;
            //通过socket获取远端计算机地址信息
            host = socket.getInetAddress().getHostAddress();
        }

        public void run() {
            PrintWriter pw = null;
            try {
                InputStream in = socket.getInputStream();
                InputStreamReader isr = new InputStreamReader(in, StandardCharsets.UTF_8);
                BufferedReader br = new BufferedReader(isr);

                /*
                    服务端这边通过socket获取输出流并进行流连接用于给客户端发送消息
                 */
                OutputStream out = socket.getOutputStream();
                OutputStreamWriter osw = new OutputStreamWriter(out, StandardCharsets.UTF_8);
                BufferedWriter bw = new BufferedWriter(osw);
                pw = new PrintWriter(bw, true);
                //将该输出流存入共享数组allOut中
//                synchronized (Server_V4.this) {
//                    //1对allOut扩容
//                    allOut = Arrays.copyOf(allOut, allOut.length + 1);
//                    //2将pw存入数组
//                    allOut[allOut.length - 1] = pw;
//                }
//                sendMessage(host+"上线了，当前在线人数:"+allOut.length);

                synchronized (allOut) {
                    allOut.add(pw);
                }
                sendMessage(host + "上线了，当前在线人数:" + allOut.size());

                String line;
                while ((line = br.readLine()) != null) {
                    sendMessage(host + "说:" + line);
                }
            } catch (IOException e) {
                //当客户端强行关闭，服务端这里readLine会抛出异常，这里就可以catch异常
            } finally {
                //将当前pw从allOut数组中删除
//                synchronized (Server_V4.this) {
//                    for (int i = 0; i < allOut.length; i++) {
//                        if (allOut[i] == pw) {
//                            allOut[i] = allOut[allOut.length - 1];
//                            allOut = Arrays.copyOf(allOut, allOut.length - 1);
//                            break;
//                        }
//                    }
//                }
//                sendMessage(host+"下线了，当前在线人数:"+allOut.length);
                synchronized (allOut) {
                    allOut.remove(pw);
                }
                sendMessage(host + "下线了，当前在线人数:" + allOut.size());
                try {
                    socket.close();//关闭socket释放底层资源
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }

        //广播消息给每一个客户端
        private void sendMessage(String line) {
            System.out.println(line);
            //将消息发送给客户端
            synchronized (allOut) {
                for (PrintWriter pw : allOut) {
                    pw.println(line);
                }
            }
        }
    }
}







